package com.ficus.road.maintain.service;

import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.core.model.coverage.CoverageFixManualUnit;
import com.ficus.road.maintain.core.model.coverage.CoverageFixTheoryGpsResponse;
import com.ficus.road.maintain.dao.CompanyDao;
import com.ficus.road.maintain.dao.RoadInfoDao;
import com.ficus.road.maintain.dao.coverage.CoverageFixManualDao;
import com.ficus.road.maintain.util.IdGeneratorUtil;
import com.ficus.road.maintain.util.LngLonUtil;
import com.ficus.road.maintain.util.MyDateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author xiawen yang
 * @date 2022/4/26 上午11:20
 */
@Service
@Slf4j
public class CoverageFixManualService {
    @Autowired
    private CoverageFixManualDao coverageFixManualDao;
    @Autowired
    private CycleNumService cycleNumService;
    @Autowired
    private RoadInfoDao roadInfoDao;
    @Autowired
    private CompanyDao companyDao;
    @Value("${coverage.theory_gps_distance}")
    private int searchDistance;

    public void createTheoryGps(double threshold) {
        String dateStr = MyDateUtil.dateFormatStr(new Date(), "yyyy-MM-dd");
        // stream()用时17m, parallelStream(0
        long startCreate = System.currentTimeMillis();
        roadInfoDao.findByQuery(new Query(Criteria.where("unit_id").ne(null))).parallelStream().forEach(roadInfo -> {
            AtomicInteger index = new AtomicInteger(0);
            List<Coordinate> coordinates = roadInfo.getCoordinates();
            if (ObjectUtils.isEmpty(coordinates)) {
                return;
            }
            System.out.println("采样前数据量: " + coordinates.size());
            coordinates = LngLonUtil.gpsSampling(coordinates, threshold);
            System.out.println("采样后数据量: " + coordinates.size());
            coordinates.parallelStream().forEach(coordinate -> {
                TheoryGps theoryGps = TheoryGps.builder()
                        .roadName(roadInfo.getRoadName())
                        .unitId(roadInfo.getUnitId())
                        .location(Arrays.asList(Double.parseDouble(coordinate.getX()), Double.parseDouble(coordinate.getY())))
                        .roadInfoId(roadInfo.getId())
                        .sequence(index.getAndIncrement())
//                        .date(dateStr)
                        .isSamples(Boolean.TRUE)
                        .build();
                coverageFixManualDao.saveOrUpdate(theoryGps);
            });
        });
        log.info("创建理论覆盖点集耗时: {}s", (System.currentTimeMillis() - startCreate) / 1000);
    }

    public void createTheoryGpsBySamples() {
        Date date = new Date();
        List<TheoryGps> sampleList = coverageFixManualDao.findByQuery(new Query(Criteria.where("is_samples").is(Boolean.TRUE)));
        String currentCycleNum = cycleNumService.getCurrentCycleNum();
        sampleList.parallelStream().forEach(theoryGps -> {
            theoryGps.setCycleNum(currentCycleNum);
            theoryGps.setIsSamples(Boolean.FALSE);
            theoryGps.setIsCovered(Boolean.FALSE);
            theoryGps.setCreationTime(date);
            theoryGps.setId(theoryGps.getRoadInfoId() + "_" + theoryGps.getSequence() + "_" + currentCycleNum);
            coverageFixManualDao.saveOrUpdate(theoryGps);
        });
    }

    public TheoryGps geoSearch(GpsHistory gpsHistory) {
        Point point = new Point(gpsHistory.getLongitude(), gpsHistory.getLatitude());
        List<AggregationOperation> aggregationList = new ArrayList<>();
        aggregationList.add(new GeoNearDocument(new Query(Criteria.where("cycle_num").is(cycleNumService.getCurrentCycleNum())), point, "distance", searchDistance));
        Aggregation agg = Aggregation.newAggregation(aggregationList);
        List<TheoryGps> result = coverageFixManualDao.aggregate(agg);
        // distance排序,返回最近的
        Optional<TheoryGps> first = result.stream().sorted(Comparator.comparing(item -> item.getDistance())).findFirst();
        if (first.isPresent()) {
            return first.get();
        }
        return null;
    }

    public List<CoverageFixManualUnit> findUncoveredPointSet(String cycleNum, String companyId) {
        List<CoverageFixManualUnit> res = new ArrayList<>();
        Company company = companyDao.fetch(companyId);
        Query query = new Query(Criteria.where("cycle_num").is(cycleNum).and("unit_id").in(company.getUnits()));
        List<TheoryGps> theoryGpsList = coverageFixManualDao.findByQuery(query);
        Map<String, List<TheoryGps>> unitIdGpsMap = theoryGpsList.stream().collect(Collectors.groupingBy(TheoryGps::getUnitId));
        unitIdGpsMap.forEach((unitId, gpsList) -> {
            CoverageFixManualUnit coverageFixManualUnit = new CoverageFixManualUnit();
            List<CoverageFixManualUnit.CoverageFixManualRoad> roadList = new ArrayList<>();
            coverageFixManualUnit.setUnitId(unitId);
            coverageFixManualUnit.setRoadList(roadList);
            Map<String, List<TheoryGps>> roadIdGpsMap = gpsList.stream().collect(Collectors.groupingBy(TheoryGps::getRoadInfoId));
            roadIdGpsMap.forEach((roadInfoId, list) -> {
                list.sort(Comparator.comparing(TheoryGps::getSequence));
                CoverageFixManualUnit.CoverageFixManualRoad coverageFixManualRoad = new CoverageFixManualUnit.CoverageFixManualRoad();
                List<CoverageFixManualUnit.CoverageFixManualRoadSec> secList = new ArrayList<>();
                coverageFixManualRoad.setRoadId(roadInfoId);
                coverageFixManualRoad.setSectionSet(secList);
                List<TheoryGps> pointSet = new ArrayList<>();
                int i = 0;
                while (i < list.size() - 1) {
                    TheoryGps pre = list.get(i++);
                    TheoryGps post = list.get(i++);
                    if (pre.getIsCovered() && post.getIsCovered()) {
                        // 相邻两个点均覆盖, 什么都不做
                    } else if (!pre.getIsCovered() && post.getIsCovered()) {
                        // 前点没覆盖, 后点覆盖 -> 生成新的roadSec和pointSet
                        pointSet.add(pre);
                        CoverageFixManualUnit.CoverageFixManualRoadSec roadSec = CoverageFixManualUnit.CoverageFixManualRoadSec.builder()
                                .secId(IdGeneratorUtil.getUUID())
                                .pointSet(new ArrayList<>(pointSet))
                                .build();
                        secList.add(roadSec);
                        pointSet.clear();
                    } else if (pre.getIsCovered() && !post.getIsCovered()) {
                        // 前点覆盖, 后点没覆盖
                        pointSet.add(post);
                    } else {
                        // 相邻两个点均没覆盖
                        pointSet.add(pre);
                        pointSet.add(post);
                    }

                }
                roadList.add(coverageFixManualRoad);
            });
            res.add(coverageFixManualUnit);
        });
        return res;
    }

    public List<TheoryGps> queryGpsListByIdList(List<String> idList) {
        return coverageFixManualDao.findByQuery(new Query(Criteria.where("_id").in(idList)));
    }

    public GpsHistory convert2GpsHistory(TheoryGps theoryGps, String carId, long timestamp) {
        return GpsHistory.builder()
                .id(carId + timestamp)
                .longitude(theoryGps.getLocation().get(0))
                .latitude(theoryGps.getLocation().get(1))
                .timestamp(timestamp)
                .location(theoryGps.getLocation())
                .carId(carId)
                .isFixed(Boolean.TRUE)
                .isManualFixed(Boolean.TRUE)
                .creationTime(new Date(timestamp * 1000))
                .build();
    }

    public CoverageFixTheoryGpsResponse.RoadDetail convert2RoadDetail(TheoryGps theoryGps) {
        return CoverageFixTheoryGpsResponse.RoadDetail.builder()
                .roadId(theoryGps.getRoadInfoId() + "_" + theoryGps.getSequence())
                .roadName(theoryGps.getRoadName())
                .coordinates(theoryGps.getLocation())
                .build();
    }

    public List<TheoryGps> findTheoryGpsSamplesByCompany(String companyId) {
        Company company = companyDao.fetch(companyId);
        return coverageFixManualDao.findByQuery(new Query(Criteria.where("is_samples").is(Boolean.TRUE).and("unit_id").in(company.getUnits())));
    }
}
